package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"

	"github.com/lucas-clemente/quic-go"
	"os"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"flag"
	"log"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/lucas-clemente/quic-go/fec"
)

var addr = "0.0.0.0:4242"
var unreliable = false
var file = ""
var deadline = 0

const message = "foobar"

//var fecScheme = &fec.XORFECScheme{}
var fecScheme, _ = fec.NewReedSolomonFECScheme(1)

// We start a server echoing data on the first stream the client opens,
// then connect with a client, send the message, and wait for its receipt.
func main() {
	server := flag.Bool("s", false, "acts like a server")
	address := flag.String("addr", "0.0.0.0:4242", "address to bind if server or to connect to if client")
	pipe := flag.String("file", "/var/www/video_pipe", "name of the pipe to open")
	nc := flag.Bool("unreliable", false, "if present, use unreliable streams")
	dl := flag.Int("deadline", 0, "Amount of time (in milliseconds) during which unreliable Stream frames should be retransmitted")
	verbose := flag.Bool("v", false, "verbose mode")
	flag.Parse()
	if *verbose {
		utils.SetLogLevel(utils.LogLevelDebug)
	} else {
		utils.SetLogLevel(utils.LogLevelInfo)
	}
	addr = *address
	unreliable = *nc
	file = *pipe
	deadline = *dl
	log.Printf(*pipe)
	//utils.SetLogLevel(utils.LogLevelDebug)
	if *server {
		EchoServer()
	} else {
		ClientMain()
	}
}

// Start a server that echos all data on the first stream opened by the client
func EchoServer() error {
	protocol.NumberOfFecPackets = 3
	//protocol.NumberOfInterleavedFECGroups = 10
	protocol.NumberOfInterleavedFECGroups = 1
	protocol.NumberOfRepairSymbols = 1
	log.Print("addr = %s", addr)
	listener, err := quic.ListenAddr(addr, generateTLSConfig(), nil)
	if err != nil {
		return err
	}
	for {
		sess, err := listener.Accept()
		sess.SetFECScheme(fecScheme)
		if err != nil {
			return err
		}
		go func () {
			stream, err := sess.AcceptStream()
			stream.SetUnreliable(unreliable)
			// TODO: it is possible to have a deadlock: when everything has been lost, the server won't move forward and the client will wait for new data
			if err != nil {
				panic(err)
			}
			file, err := os.Open(file)
			_, err = io.Copy(stream, file)
			log.Printf("Done")
		}()
	}
	sess, err := listener.Accept()
	if err != nil {
		return err
	}
	stream, err := sess.AcceptStream()
	stream.SetUnreliable(unreliable)
	if err != nil {
		panic(err)
	}
	file, err := os.Open("/var/www/video_pipe")
	_, err = io.Copy(stream, file)
	return err
}

func ClientMain() error {
	session, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, nil)
	session.SetFECScheme(fecScheme)
	if err != nil {
		return err
	}

	stream, err := session.OpenStreamSync()
	if err != nil {
		return err
	}
	desynchronizationBytes := uint64(25000)
	stream.SetReplayBufferSize(desynchronizationBytes)
	_, err = stream.Write([]byte(message))
	if err != nil {
		return err
	}
	// stream.SetReliabilityDeadline(1*time.Millisecond)
	_, err = io.CopyBuffer(os.Stdout, stream, make([]byte, 1500))
	return nil
}

// A wrapper for io.Writer that also logs the message.
type loggingWriter struct{ io.Writer }

func (w loggingWriter) Write(b []byte) (int, error) {
	fmt.Printf("Server: Got '%s'\n", string(b))
	return w.Writer.Write(b)
}

// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
	key, err := rsa.GenerateKey(rand.Reader, 1024)
	if err != nil {
		panic(err)
	}
	template := x509.Certificate{SerialNumber: big.NewInt(1)}
	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
	if err != nil {
		panic(err)
	}
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

	tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
	if err != nil {
		panic(err)
	}
	return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
}
